package com.lancabbage.gorgeous.utils.doc;

import com.lancabbage.gorgeous.bean.dto.ClassFieldDto;
import com.lancabbage.gorgeous.bean.dto.ClassInfoDto;
import com.lancabbage.gorgeous.bean.po.ClassInfo;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.*;
import com.thoughtworks.qdox.model.impl.DefaultJavaParameterizedType;
import com.thoughtworks.qdox.model.impl.DefaultJavaType;
import com.thoughtworks.qdox.model.impl.DefaultJavaTypeVariable;
import com.thoughtworks.qdox.model.impl.DefaultJavaWildcardType;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.lancabbage.gorgeous.utils.doc.NotesConfigUtils.s;

/**
 * @author: lanyanhua
 * @date: 2020/12/4 8:38 下午
 * @Description:
 */
public class ClassInfoUtils {

    public final List<String> baseDataType;
    //    public static List<String> notSetField = Arrays.asList();
    public final List<String> arrayType;
    private final AnnotationUtils annotationUtils;
    private final JavaProjectBuilder javaProjectBuilder;
    /**
     * class Map
     */
    public Map<ClassKey, ClassInfoDto> classMap;


    public ClassInfoUtils(AnnotationUtils annotationUtils, JavaProjectBuilder javaProjectBuilder) {
        this.annotationUtils = annotationUtils;
        this.javaProjectBuilder = javaProjectBuilder;
        classMap = new HashMap<>();
        this.baseDataType = NotesConfigUtils.getBaseDataType();
        this.arrayType = NotesConfigUtils.getArrayType();

    }

    public Map<ClassKey, ClassInfoDto> getClassMap() {
        return classMap;
    }

    public ClassInfoDto getClassInfo(JavaType type) {
        return getClassInfo(type, new HashMap<>());
    }

    /**
     * 获取入参出餐 class
     *
     * @param type        class类型
     * @param paradigmMap 范型值
     * @return classInfo
     */
    public ClassInfoDto getClassInfo(JavaType type, Map<String, ClassInfoDto> paradigmMap) {
        String packagePath = type.getGenericFullyQualifiedName();
        JavaClass doc = (JavaClass) type;
        String className = type instanceof DefaultJavaWildcardType ? "Object" : doc.getName();
        boolean isArr = doc.isArray();
        if (isArr) {
            className += "[]";
        }
        //todo 当前类
        //基本数据类型直接返回
        if (baseDataType.contains(className) && !isArr) {
            return new ClassInfoDto(className);
        }
        //赋值返回Class数据
        ClassKey classKey = new ClassKey();
        classKey.setName(className);
        classKey.setPackagePath(packagePath);

        //范型
        // getActualTypeArguments is null  typeParameters 类型 取 paradigmMap | object 有类型还需要处理包路径
        // typeParameters is null 目前只有list有这个问题
        List<ClassInfoDto> paradigmList = new ArrayList<>();
        List<JavaTypeVariable<JavaGenericDeclaration>> typeParameters = doc.getTypeParameters();
        if (type instanceof JavaParameterizedType) {
            paradigmList = ((JavaParameterizedType) type).getActualTypeArguments().stream()
                    .map(this::getClassInfo)
                    .collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(typeParameters) || !paradigmList.isEmpty()) {
                //list
                if (typeParameters.size() != paradigmList.size()) {
                    typeParameters = paradigmList.stream()
                            .map(i -> new DefaultJavaTypeVariable<>(i.getClassName(), null))
                            .collect(Collectors.toList());
                }
                int i = 0;
                boolean isPutPac = false;
                for (JavaTypeVariable<JavaGenericDeclaration> typeParameter : typeParameters) {
                    ClassInfoDto v = paradigmMap.get(typeParameter.getName());
                    //  java.util.List<T> T存在类型 修改包名
                    if (v == null) {
                        v = paradigmList.get(i);
                        paradigmMap.put(typeParameter.getName(), v);
                    } else {
                        isPutPac = true;
                        paradigmList.set(i, v);
                    }
                    //获取范型classKey
                    i++;
                }

                //数组修改类名
                if (arrayType.contains(className)) {
                    classKey.setName(paradigmList.get(0).getClassName() + "[]");
                }
                if (isPutPac) {
                    //  java.util.List<T> 修改包名
                    packagePath = type.getBinaryName() + "<" +
                            paradigmList.stream().map(ClassInfo::getPackagePath).collect(Collectors.joining(","))
                            + ">";
                    classKey.setPackagePath(packagePath);
                }
            }
        }
        //从classMap中获取class
        ClassInfoDto classInfoDto = classMap.get(classKey);
        if (classInfoDto != null) {
            return classInfoDto;
        }
        classInfoDto = new ClassInfoDto();
        classInfoDto.setParadigmList(paradigmList);

        //先申明对象放入classMap中 解决循环依赖问题
        classMap.put(classKey, classInfoDto);
        classInfoDto.setClassName(classKey.getName());
        //class路径不为null，价值class信息
        classInfoDto.setClassPath(classKey.getPackagePath());
        classInfoDto.setPackagePath(classKey.getPackagePath());
        //不用赋值字段的赋值范型，用赋值字段的范型当字段数据类型用
        List<ClassFieldDto> fieldDtoList = new ArrayList<>();
        classInfoDto.setClassFieldList(fieldDtoList);
        //数组赋值类型
        if (arrayType.contains(className) || isArr) {
            ClassFieldDto fieldDto = new ClassFieldDto();
            fieldDtoList.add(fieldDto);
            fieldDto.setParamName("value");
            fieldDto.setParamDescribe("");
            ClassInfoDto c;
            JavaClass javaClass;
            if (isArr) {
                //数组
                c = getClassInfo(((DefaultJavaType) type).getComponentType());
                fieldDto.setTypeClass(c);
                fieldDto.setType(c.getClassName());
            } else if (!paradigmList.isEmpty()
                    && (c = paradigmList.get(0)) != null
                    && (c.getBaseType() != null
                    || ((javaClass = javaProjectBuilder.getClassByName(c.getClassPath())) != null
                    && (javaClass.isPrimitive() || javaClass.getPackage() != null)))) {
                //范型
                fieldDto.setTypeClass(c);
                fieldDto.setType(c.getClassName());
            } else {
                fieldDto.setType("Object");
            }
            return classInfoDto;
        }

        //父类
        JavaType superclass = doc.getSuperClass();
        //描述
        classInfoDto.setClassDescribe(doc.getComment());
        //字段
        List<JavaField> fields = doc.getFields();
        for (JavaField field : fields) {
            ClassFieldDto fieldDto = new ClassFieldDto();
            fieldDtoList.add(fieldDto);
            fieldDto.setParamName(field.getName());
            //字段注释
            annotationUtils.setParmTag(NotesConfigUtils.getFieldTag());
            String tagDesc = annotationUtils.getTagDesc(field.getTags());
            annotationUtils.setParmTag(NotesConfigUtils.getFieldAnnotation());
            String annotationDesc = annotationUtils.getAnnotationDesc(field.getAnnotations());
            fieldDto.setParamDescribe(s(annotationDesc, tagDesc, field.getComment()));
            JavaClass fType = field.getType();
            String name = fType.getName();

            //数据类型
            ClassInfoDto dataType;
            if ((dataType = paradigmMap.get(name)) == null) {
                Map<String, ClassInfoDto> paradigmMap1 = new HashMap<>();
                List<JavaType> types = ((DefaultJavaParameterizedType) fType).getActualTypeArguments();
                if (!types.isEmpty()) {
                    //当前字段是范型字段 并且引用了当前范型 赋值范型,
                    //class Page<T>{
                    // list<T> list
                    //}
                    //当前page对象能获取到 T 的实际类型 但是list也是范型引用了 T 这个list就获取不到，工具没有传导到字段
                    for (JavaType type1 : types) {
                        if (paradigmMap.containsKey(((JavaClass) type1).getName())) {
                            paradigmMap1 = paradigmMap;
                            break;
                        }
                    }
                }
                //paradigmMap get 为null 就调用getClassInfo获取
                dataType = getClassInfo(fType, paradigmMap1);
            }
            //为基本数据类型
            if (fieldDto.setType(dataType.getBaseType()) == null) {
                //为引用数据类型
                fieldDto.setTypeClass(dataType);
                fieldDto.setType(dataType.getClassName());
            }
        }
        //父类字段
        if (superclass != null && !superclass.toString().contains("java.lang.")) {
            //获取父类字段
            ClassInfoDto classInfo = getClassInfo(superclass);
            if (classInfo.getClassFieldList() != null) {
                fieldDtoList.addAll(classInfo.getClassFieldList());
            }
        }

        return classInfoDto;
    }

}
